Skip to content

配列の要素を区切り文字付きの文字列に連結する snippet

リストを1つの区切り文字付き文字列へ平坦化します。CSV 行、IN 句、ログのフィールドなどです。区切り文字は要素の間にだけ置かれ、最後の要素の後には決して付いてはならないため、末尾にカンマが残る手書きループが典型的なバグです。もう1つの罠は join が雑多な要素をどう扱うかです。JavaScript と Ruby は null/undefined を '' に強制変換する一方、Python、Java、Kotlin は 'None'/'null' を平然と出力します。JavaScript はネストした配列を1レベル平坦化し、PHP はそれらに警告を出します。ほぼすべての高水準言語にワンコールの join がありますが、C にはありません(サイズを意識したループを下に示します)。SQL では配列の連結(array_to_string)と行の集約(string_agg)は別の操作です。

リストを1つの区切り文字付き文字列へ平坦化します。CSV 行、IN 句、ログのフィールドなどです。区切り文字は要素の間にだけ置かれ、最後の要素の後には決して付いてはならないため、末尾にカンマが残る手書きループが典型的なバグです。もう1つの罠は join が雑多な要素をどう扱うかです。JavaScript と Ruby は null/undefined を '' に強制変換する一方、Python、Java、Kotlin は 'None'/'null' を平然と出力します。JavaScript はネストした配列を1レベル平坦化し、PHP はそれらに警告を出します。ほぼすべての高水準言語にワンコールの join がありますが、C にはありません(サイズを意識したループを下に示します)。SQL では配列の連結(array_to_string)と行の集約(string_agg)は別の操作です。

Runnable recipe · 15 languages
Text & Parsingstringsarraysjoinformatting

Every language

15 languages, copy-ready. One at a time with syntax highlighting, or all inline.

SQLSQLrunnable
-- PostgreSQL: join an ARRAY with a delimiter
SELECT array_to_string(ARRAY['go', 'rust', 'zig'], ', ') AS langs;
-- langs: go, rust, zig

-- Joining ROWS is a different job — aggregate, don't array-join:
SELECT string_agg(name, ', ' ORDER BY name) AS langs
FROM (VALUES ('zig'), ('go'), ('rust')) AS t(name);
-- langs: go, rust, zig

array_to_string joins an array; string_agg (STRING_AGG in SQL Server, GROUP_CONCAT in MySQL) concatenates rows — mixing them up is the perennial SQL mistake. NULLs vanish in both by default; MySQL's GROUP_CONCAT needs SEPARATOR ', ' and truncates at group_concat_max_len (default 1024).

Run in the SQL playground →
JSJavaScript
const langs = ['go', 'rust', 'zig'];
console.log(langs.join(', ')); // go, rust, zig

// join() COERCES instead of throwing — know the rules:
console.log([1, null, undefined, [2, 3]].join('|')); // 1|||2,3
// null/undefined become '' and nested arrays flatten ONE level,
// so the separator inside the nested array is ',' — not yours.

[] .join() returns '' (never throws), and a Symbol element is the only thing that raises TypeError. If nulls should disappear rather than render as empty fields, filter them before joining.

TSTypeScript
const parts: string[] = ['a', 'b', 'c'];
const csv: string = parts.join(',');

// Typed arrays of string | null join fine at runtime but lie about
// content — scrub before joining to keep the type honest:
const dirty: (string | null)[] = ['a', null, 'c'];
const clean: string = dirty.filter((p): p is string => p !== null).join(',');
console.log(csv, clean); // a,b,c a,c

join() accepts readonly arrays too (readonly string[]), so functions taking lists they will not mutate can join without copying.

GoGo
package main

import (
	"fmt"
	"strings"
)

func main() {
	langs := []string{"go", "rust", "zig"}
	fmt.Println(strings.Join(langs, ", ")) // go, rust, zig
}

strings.Join takes []string only — convert other element types first with a map. It never appends the trailing separator and []string{} yields "". For numbers, fmt.Sprint(a, b, ...) inserts spaces between operands, so it is not a join.

RsRust
fn main() {
    let langs = vec!["go", "rust", "zig"];
    println!("{}", langs.join(", ")); // go, rust, zig

    // Non-strings: map to String first, then join.
    let nums: Vec<String> = [1, 2, 3].iter().map(|n| n.to_string()).collect();
    println!("{}", nums.join("-")); // 1-2-3
}

join comes from the Join slice trait — it works on Vec<&str>, Vec<String> and iterators collected into either, and borrows nothing you do not hand it. The separator is a str, and an empty slice yields an empty String.

PHPPHP
<?php
$langs = ['go', 'rust', 'zig'];
echo implode(', ', $langs), PHP_EOL; // go, rust, zig

// null silently becomes ''; scalars are cast; nested arrays WARN:
$dirty = ['a', null, 3];
echo implode('|', $dirty), PHP_EOL; // a||3

implode (alias join) is argument-order agnostic since PHP 8 — implode($sep, $arr) and implode($arr, $sep) both work. A nested array raises 'Array to string conversion', so map first with array_map('strval', $arr) for mixed data.

PyPython
langs = ["go", "rust", "zig"]
print(", ".join(langs))                          # go, rust, zig
print("-".join(str(n) for n in [1, None, 3]))   # 1-None-3 — None is NOT skipped

# Non-str elements raise TypeError, and the message is the teacher:
try:
    ", ".join(["a", 1])
except TypeError as err:
    print(err)  # sequence item 1: expected str instance, int found

join is a STRING method, not a list method — '" ".join(items)', never 'items.join(...)'. Contrast with JavaScript: Python stringifies None as 'None' rather than dropping it to ''.

CC
#include <stdio.h>
#include <string.h>

/* Join n parts with sep into out (size bytes). NUL-terminates, truncates
   safely on overflow, and never emits the separator after the last part.
   Returns out. */
char *join(char *out, size_t size, const char *sep, const char **parts, size_t n) {
    size_t pos = 0;
    out[0] = '\0';
    for (size_t i = 0; i < n; i++) {
        size_t len = strlen(parts[i]);
        size_t sep_len = i > 0 ? strlen(sep) : 0;
        if (pos + sep_len + len + 1 > size) {
            break; /* not enough room for part + NUL — stop here */
        }
        if (i > 0) {
            memcpy(out + pos, sep, sep_len);
            pos += sep_len;
        }
        memcpy(out + pos, parts[i], len);
        pos += len;
        out[pos] = '\0';
    }
    return out;
}

int main(void) {
    const char *parts[] = {"go", "rust", "zig"};
    char buf[64];
    printf("%s\n", join(buf, sizeof buf, ", ", parts, 3)); /* go, rust, zig */
    return 0;
}

No stdlib join exists — the bugs are always the same two: a separator after the last element and a buffer overflow. The explicit size argument and the room-for-NUL check are what make this safe; snprintf into a cursor is the other accepted idiom.

C++C++
#include <iostream>
#include <string>
#include <vector>

// Separator BETWEEN elements only — the `if (i > 0)` guard is what
// kills the trailing-separator bug. C++23 adds std::views::join_with.
std::string join(const std::vector<std::string>& parts, const std::string& sep) {
    std::string out;
    for (std::size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) out += sep;
        out += parts[i];
    }
    return out;
}

int main() {
    std::cout << join({"go", "rust", "zig"}, ", ") << '\n'; // go, rust, zig
}

reserve() the output when sizes are known, or fold with std::accumulate for one-liners. fmt::join (or std::format's ranged join in C++23) formats non-strings without pre-converting.

C#C#
string[] langs = { "go", "rust", "zig" };
Console.WriteLine(string.Join(", ", langs)); // go, rust, zig

// Accepts IEnumerable<T> and calls ToString; null renders as empty:
Console.WriteLine(string.Join(", ", new object?[] { 1, null, "x" })); // 1, , x

string.Join overloads take params arrays, IEnumerable<string>, and IEnumerable<T> — so LINQ sequences join without materializing. For comma-critical output (CSV), quote fields separately; Join does no escaping.

JvJava
import java.util.List;

public class JoinDemo {
    public static void main(String[] args) {
        List<String> langs = List.of("go", "rust", "zig");
        System.out.println(String.join(", ", langs)); // go, rust, zig
    }
}

String.join accepts CharSequence only (String, StringBuilder, ...) — map other types first. Null elements are NOT dropped: String.join(",", "a", null) prints 'a,null'. For streams use Collectors.joining(", ").

SwSwift
let langs = ["go", "rust", "zig"]
print(langs.joined(separator: ", ")) // go, rust, zig

// Non-strings: map to String first.
print([1, 2, 3].map(String.init).joined(separator: "-")) // 1-2-3

joined() on [String] yields String, but joined(separator:) on a slice of Substring views is also fine. The no-separator joined() concatenates directly. Empty arrays yield "" — never an optional.

KtKotlin
fun main() {
    val langs = listOf("go", "rust", "zig")
    println(langs.joinToString(", ")) // go, rust, zig

    println(listOf(1, null, 3).joinToString("-")) // 1-null-3
}

joinToString carries the kitchen sink: prefix = "[", postfix = "]", limit = 3, truncated = "…" — logging-friendly out of the box. Like Java, null elements render as the literal 'null'.

RbRuby
langs = %w[go rust zig]
puts langs.join(', ') # go, rust, zig

# nil stringifies to '' — and nested arrays are flattened RECURSIVELY,
# with your separator applied inside them too:
puts [1, nil, [2, 3]].join('|').inspect # "1||2|3"

Array#join flattens nested arrays to arbitrary depth, which quietly changes your delimiter structure. %w[...] literals are the idiomatic source of string arrays; [].join(',') is "".

ZigZig
const std = @import("std");

pub fn main() !void {
    const parts = [_][]const u8{ "go", "rust", "zig" };
    const sep = ", ";

    // Size the buffer exactly (saturating sub guards the empty case),
    // allocate once, then fill — no trailing separator.
    var total: usize = 0;
    for (parts) |p| total += p.len;
    total += sep.len * (parts.len -| 1);

    const a = std.heap.page_allocator;
    const out = try a.alloc(u8, total);
    defer a.free(out);

    var pos: usize = 0;
    for (parts, 0..) |p, i| {
        if (i > 0) {
            @memcpy(out[pos..][0..sep.len], sep);
            pos += sep.len;
        }
        @memcpy(out[pos..][0..p.len], p);
        pos += p.len;
    }
    std.debug.print("{s}\n", .{out}); // go, rust, zig
}

No stdlib join — allocate total + separators in one shot, then memcpy each part. `parts.len -| 1` (saturating) keeps the empty-array case from underflowing, and Zig 0.11+ multi-object for loops give you the index for the separator test.